﻿// Dependencies: 
//  1. jQuery
//  2. jQuery.Validate
//  3. jQuery.MetaData

using System;
using System.Collections.Generic;
using System.Text;
using System.Web;

namespace BigfootMVC.Helpers
{
    
    /// <summary>
    /// Html tag render mode enumeration
    /// </summary>
    public enum RenderMode
    {
        /// <summary>
        /// Renders using a corresponding </tag>
        /// </summary>
        Normal,
        /// <summary>
        /// It is a selfclosing tag. Like for inputs
        /// </summary>
        SelfClose,
        /// <summary>
        /// Does not ouput any tag closing. Useful when ouputing the beginning tag of a form but you don't want to close it.
        /// </summary>
        openTagOnly
    }

    /// <summary>
    /// The tag builder class is the heart of the html builder. It is used to create any html tag
    /// using a fluid interface. It has a ton of convinience methods to aid in the bulding of tags.
    /// It goes beyond the mere building of tags though and also adds javscript here and there to create
    /// interactivity like rollover effects, rollover effects with sprites etc.
    /// 
    /// It also relies on the metadata method for adding jquery.validation to input elements.
    /// 
    /// The tagbuilder class also understands the concept of input and label combinations to aid
    /// in minimizing the amount of code you have to write to create the very common label input validation 
    /// combination
    /// </summary>
    public class TagBuilder
    {
        private string TagName { get; set; }
        private string ElementId { get; set; }
        private Dictionary<string, string> TagAttributes { get; set; }
        public RenderMode? Mode { get; set; }

        /// <summary>
        /// Returns this instance's parent if it is inside another collection
        /// </summary>
        public TagBuilder Parent { get; set; }

        /// <summary>
        /// Returns the last added child. If you want to access all children use the children collection instead
        /// </summary>
        public TagBuilder Child { get; set; }
        public List<TagBuilder> Children { get; set; }

        private string _innerText = "";
        private string _innerHtml = "";
        private string _label = "";
        private string _labelclass;
        private string _labelWidth;
        private List<SelectItem> _options = new List<SelectItem>();
        private string _selectedOption = "";

        /// <summary>
        /// Creates a new TagBuilder by specifying the name of the tag you want to create i.e. h1
        /// </summary>
        /// <param name="tagName">The html tag name to use i.e. h1</param>
        public TagBuilder(string tagName)
        {
            TagAttributes = new Dictionary<string, string>();
            Children = new List<TagBuilder>();
            TagName = tagName;
            Mode = new RenderMode?();
        }


        /// <summary>
        /// Creates a new TagBuilder by specifying the name of the tag you want to create i.e. h1
        /// </summary>
        /// <param name="tagName">The html tag name to use i.e. h1</param>
        /// <param name="parent">Create a tag as an embeded child of another tag for hiarchical tag ouput</param>
        public TagBuilder(string tagName, TagBuilder parent)
        {
            TagAttributes = new Dictionary<string, string>();
            Children = new List<TagBuilder>();
            TagName = tagName;
            Parent = parent;
            Mode = new RenderMode?();
        }

        /// <summary>
        /// Creates a new TagBuilder by specifying the name of the tag you want to create i.e. h1
        /// </summary>
        /// <param name="tagName">The html tag name to use i.e. h1</param>
        /// <param name="mode">Specifies the render mode for the. SelfClosing etc.</param>
        public TagBuilder(string tagName, RenderMode mode)
        {
            TagAttributes = new Dictionary<string, string>();
            Children = new List<TagBuilder>();
            TagName = tagName;
            Mode = mode;
        }

        /// <summary>
        /// Creates a new TagBuilder by specifying the name of the tag you want to create, id and name to use and the class name to assign to it
        /// </summary>
        /// <param name="tagName">The html tag name to use i.e. h1</param>
        /// <param name="idAndName">The ID and Name to assign</param>
        /// <param name="className">The className to assign to the task</param>
        public TagBuilder(string tagName, string idAndName, string className)
        {
            TagAttributes = new Dictionary<string, string>();
            Children = new List<TagBuilder>();
            TagName = tagName;
            Mode = new RenderMode?();
            AddAttribute("id", idAndName);
            AddAttribute("name", idAndName);
            AddAttribute("class", className);
        }

        /// <summary>
        /// Adds a child to the children tags collection and returns the newaly added child
        /// </summary>
        /// <param name="tagname">The tag name for the child to add</param>
        /// <returns>The added child. Use the Parent property to get back to parent builder</returns>
        public TagBuilder AddChild(string tagname)
        {
            return AddChild(new TagBuilder(tagname));
        }

        /// <summary>
        /// Adds a child TagBuilder to the children tags collection and returns the newaly added child. 
        /// This is useful when calling render on the parent to make sure that it renders all of it's 
        /// children as well
        /// </summary>
        /// <param name="child">The TagBuilder object to add</param>
        /// <returns>The added child. Use the Parent property to get back to parent builder</returns>
        public TagBuilder AddChild(TagBuilder child)
        {
            child.Parent = this;
            Children.Add(child);
            return child;
        }


        /// <summary>
        /// Retreives a child element by its tag name
        /// </summary>
        /// <param name="tagName">The tag to retreive i.e. img or label etc.</param>
        /// <returns>The Child TagBuilder</returns>
        public TagBuilder GetChildByTagName(string tagName)
        {
            foreach (var child in Children)
            {
                if (child.TagName.ToLowerInvariant() == tagName.ToLowerInvariant())
                    return child;
            }
            return null;
        }

        /// <summary>
        /// Retreives a child element by the element id assigned through the ID function of the tagbuilder class
        /// </summary>
        /// <param name="id">The ID of the element to retreive</param>
        /// <returns>The Child TagBuilder</returns>
        public TagBuilder GetChildById(string id)
        {
            foreach (var child in Children)
            {
                if (child.ElementId.ToLowerInvariant() == id.ToLowerInvariant())
                    return child;
            }
            return null;
        }

        /// <summary>
        /// Adds a class to the element only if a certain condition is met
        /// </summary>
        /// <param name="condition">Condition to use</param>
        /// <param name="classname">Class name to add</param>
        public TagBuilder AddClassIf(bool condition, string classname)
        {
            return AddClassIf(condition, classname, string.Empty);
        }

        /// <summary>
        /// Adds a class to the element only if a certain condition is met. If it adds another class
        /// </summary>
        /// <param name="condition">Condition to use</param>
        /// <param name="classname">Class name to add when condition is true</param>
        /// <param name="otherclassname">Class name to add when condition is false</param>
        public TagBuilder AddClassIf(bool condition, string classname, string otherclassname)
        {
            if (condition)
            {
                AddClass(classname);
            }
            else if (string.IsNullOrEmpty(otherclassname) == false)
            {
                AddClass(otherclassname);
            }
            return this;
        }

        public TagBuilder Add508Style()
        {
            return addStyle("position:absolute; left:-5000px; top:0px; width:1px; height:1px; overflow:hidden;");
        }

        /// <summary>
        /// Adds a class(es) to the tag
        /// </summary>
        /// <param name="classes">List of tags to add</param>
        public TagBuilder AddClass(params string[] classes)
        {
            foreach (var c in classes)
            {
                MergeAttribute("class", c);
            }
            return this;
        }

        /// <summary>
        /// Adds a style attribute if not present to the tag and specifies the width in pixes
        /// </summary>
        /// <param name="value">Width in pixes</param>
        public TagBuilder width(int value)
        {
            return width(value.ToString(), value.ToString());
        }

        /// <summary>
        /// Adds a style attribute if not present to the tag and specifies the width in pixes for both the tag and its label
        /// </summary>
        /// <param name="inputWidth">Width in pixels for the input</param>
        /// <param name="labelWidth">Width in pixels for the label</param>
        public TagBuilder width(int inputWidth, int labelWidth)
        {
            return width(inputWidth.ToString(), labelWidth.ToString());
        }

        /// <summary>
        /// Adds a style attribute if not present to the tag and specifies the width
        /// </summary>
        /// <param name="value">Width value</param>
        public TagBuilder width(string value)
        {
            return width(value, value);
        }

        /// <summary>
        /// Adds a style attribute if not present to the tag and specifies the width for both the tag and its label
        /// </summary>
        /// <param name="inputWidth">Width for the input</param>
        /// <param name="labelWidth">Width for the label</param>
        public TagBuilder width(string inputWidth, string labelWidth)
        {
            AppendPixels(ref inputWidth);
            AppendPixels(ref labelWidth);
            _labelWidth = labelWidth;
            return MergeAttribute("style", "width: " + inputWidth);
        }

        /// <summary>
        /// Adds a background color to the element
        /// </summary>
        /// <param name="color">The background color to apply (optional)</param>
        public TagBuilder bgcolor(string color)
        {
            return addStyle("background-color: " + color);
        }

        /// <summary>
        /// Adds a background iamge to the element
        /// </summary>
        /// <param name="imageUrl">The url to the background image</param>
        public TagBuilder bgimage(string imageUrl)
        {
            return addStyle("background-image: url('" + imageUrl + "')");
        }

        /// <summary>
        /// Adds the background image to the element
        /// </summary>
        /// <param name="imageUrl">The background image</param>
        /// <param name="repeatHorizontally">True to repeat horizontally</param>
        /// <param name="repeatVertically">True to repeat vertically</param>
        public TagBuilder bgimage(string imageUrl, bool repeatHorizontally, bool repeatVertically)
        {
            return addStyle("background-image: url('" + imageUrl + "')")
                    .bgrepeat(repeatHorizontally, repeatVertically);
        }

        /// <summary>
        /// Adds the background image to the element
        /// </summary>
        /// <param name="imageUrl">The background image</param>
        /// <param name="position">The background position</param>
        /// <param name="repeatHorizontally">True to repeat horizontally</param>
        /// <param name="repeatVertically">True to repeat vertically</param>
        public TagBuilder bgimage(string imageUrl, bool repeatHorizontally,bool repeatVertically, string position)
        {
            return addStyle("background-image: url('" + imageUrl + "')")
                    .bgposition(position)
                    .bgrepeat(repeatHorizontally, repeatVertically);
        }

        /// <summary>
        /// Adds the background-repeat css property to the element
        /// </summary>
        /// <param name="repeatHorizontally">True to repeat horizontally</param>
        /// <param name="repeatVertically">True to repeat vertically</param>
        public TagBuilder bgrepeat(bool repeatHorizontally, bool repeatVertically)
        {
            if (repeatHorizontally && !repeatVertically)
            {
                addStyle("background-repeat: repeat-x");
            }
            else if (!repeatHorizontally && repeatVertically)
            {
                addStyle("background-repeat: repeat-y");
            }
            else if (!repeatHorizontally && !repeatVertically)
            {
                addStyle("background-repeat: no-repeat");
            }

            return this;
        }




        private void AppendPixels(ref string value)
        {
            if (value.Trim().EndsWith("%") == false && value.Trim().EndsWith("px") == false)
            {
                value += "px";
            }
        }

        /// <summary>
        /// Adds a style attribute if not present to the tag and specifies the width for the label
        /// </summary>
        /// <param name="value">Width value</param>
        public TagBuilder labelwidth(string value)
        {
            if (value.Trim().EndsWith("%") == false && value.Trim().EndsWith("px") == false)
            {
                value += "px";
            }
            _labelWidth = value;
            return this;
        }
        
        /// <summary>
        /// Adds a style attribute if not present to the tag and specifies the height for the element
        /// </summary>
        /// <param name="value">Height value</param>
        public TagBuilder height(string value)
        {
            return MergeAttribute("style", "height: " + value);
        }

        /// <summary>
        /// Adds a style attribute if not present to the tag and specifies the width and height for the tag
        /// </summary>
        /// <param name="width">Width value in pixels</param>
        /// <param name="height">Height value in pixels</param>
        public TagBuilder size(int width, int height)
        {
            return addStyle(string.Format("width: {0}px; height: {1}px", width, height));
        }

        /// <summary>
        /// Adds a style attribute if not present to the tag and specifies the font size for the tag
        /// </summary>
        /// <param name="pixels">Font-size in pixels</param>
        public TagBuilder fontsize(int pixels)
        {
            return MergeAttribute("style", "font-size: " + pixels + "px");
        }

        /// <summary>
        /// Adds a style attribute if not present to the tag and specifies the font size for the tag
        /// </summary>
        /// <param name="value">Font-size value</param>
        public TagBuilder fontsize(string value)
        {
            return MergeAttribute("style", "font-size: " + value);
        }

        /// <summary>
        /// Adds a style attribute if not present to the tag and specifies the font-weight for the tag as bold
        /// </summary>
        public TagBuilder bold()
        {
            return MergeAttribute("style", "font-weight: bold");
        }

        /// <summary>
        /// Adds a style attribute if not present to the tag and specifies the padding-top for the tag
        /// </summary>
        /// <param name="pixels">Padding-top in pixels</param>
        public TagBuilder padTop(int pixels)
        {
            return addStyle(string.Format("padding-top: {0}px", pixels));
        }

        /// <summary>
        /// Adds a style attribute if not present to the tag and specifies the padding-left for the tag
        /// </summary>
        /// <param name="pixels">Padding-left in pixels</param>
        public TagBuilder padLeft(int pixels)
        {
            return addStyle(string.Format("padding-left: {0}px", pixels));
        }

        /// <summary>
        /// Adds a style attribute if not present to the tag and specifies the padding-right for the tag
        /// </summary>
        /// <param name="pixels">Padding-right in pixels</param>
        public TagBuilder padRight(int pixels)
        {
            return addStyle(string.Format("padding-right: {0}px", pixels));
        }

        /// <summary>
        /// Adds a style attribute if not present to the tag and specifies the padding-bottom for the tag
        /// </summary>
        /// <param name="pixels">Padding-bottom in pixels</param>
        public TagBuilder padBottom(int pixels)
        {
            return addStyle(string.Format("padding-bottom: {0}px", pixels));
        }

        /// <summary>
        /// Adds a style attribute if not present to the tag and specifies the padding for the tag in pixes
        /// </summary>
        public object pad(int allSidePx)
        {
            return addStyle(string.Format("padding: {0}px", allSidePx));
        }

        /// <summary>
        /// Adds a style attribute if not present to the tag and specifies the padding for the tag in pixes
        /// </summary>
        public TagBuilder pad(int topPx, int rightPx, int bottomPx, int leftPix)
        {
            return addStyle(string.Format("padding: {0}px {1}px {2}px {3}px", topPx, rightPx, bottomPx, leftPix));
        }

        /// <summary>
        /// Adds a style attribute if not present to the tag and specifies the margin-top for the tag
        /// </summary>
        /// <param name="pixels">Margin-top in pixels</param>
        public TagBuilder marginTop(int pixels)
        {
            return addStyle(string.Format("margin-top: {0}px", pixels));
        }

        /// <summary>
        /// Adds a style attribute if not present to the tag and specifies the margin-left for the tag
        /// </summary>
        /// <param name="pixels">Margin-left in pixels</param>
        public TagBuilder marginLeft(int pixels)
        {
            return addStyle(string.Format("margin-left: {0}px", pixels));
        }

        /// <summary>
        /// Adds a style attribute if not present to the tag and specifies the margin-right for the tag
        /// </summary>
        /// <param name="pixels">Margin-right in pixels</param>
        public TagBuilder marginRight(int pixels)
        {
            return addStyle(string.Format("margin-right: {0}px", pixels));
        }

        /// <summary>
        /// Adds a style attribute if not present to the tag and specifies the margin-bottom for the tag
        /// </summary>
        /// <param name="pixels">Margin-bottom in pixels</param>
        public TagBuilder marginBottom(int pixels)
        {
            return addStyle(string.Format("margin-bottom: {0}px", pixels));
        }

        /// <summary>
        /// Adds a style attribute if not present to the tag and specifies the margin for the tag in pixels
        /// </summary>
        public TagBuilder margin(int topPx, int rightPx, int bottomPx, int leftPix)
        {
            return addStyle(string.Format("margin: {0}px {1}px {2}px {3}px", topPx, rightPx, bottomPx, leftPix));
        }

        /// <summary>
        /// Adds a style attribute if not present to the tag and specifies the display as inline-block
        /// </summary>
        public TagBuilder inlineblock()
        {
            return addStyle("display: inline-block");
        }

        /// <summary>
        /// Adds a style attribute if not present to the tag and specifies the text-decoration as none
        /// </summary>
        public TagBuilder noDecoration()
        {
            return MergeAttribute("style", "text-decoration: none");
        }

        /// <summary>
        /// Adds a style attribute if not present to the tag and specifies the color
        /// </summary>
        public TagBuilder color(string value)
        {
            return MergeAttribute("style", "color: " + value);
        }

        public TagBuilder bgposition(string value)
        {
            return addStyle("background-position: " + value);
        }

        /// <summary>
        /// Adds the method attribte to the tag. Used in the form tag
        /// </summary>
        /// <param name="value">post, get etc.</param>
        public TagBuilder method(string value)
        {
            return AddAttribute("method", value);
        }

        /// <summary>
        /// Adds the multipart attribute to a form element
        /// </summary>
        public TagBuilder multipart()
        {
            return AddAttribute("enctype", "multipart/form-data");
        }

        /// <summary>
        /// Sets the action on a form element
        /// </summary>
        /// <param name="url">The url to submit the form to</param>
        public TagBuilder action(string url)
        {
            return AddAttribute("action", url);
        }

        /// <summary>
        /// Sets the alt tag on an image element
        /// </summary>
        /// <param name="value">The alt text</param>
        public TagBuilder Alt(string value)
        {
            return AddAttribute("alt", value);
        }

        /// <summary>
        /// Sets the InnerText value of the element. I.E. TextArea inner text
        /// </summary>
        public TagBuilder text(string value)
        {
            return InnerText(value);
        }

        /// <summary>
        /// Sets the InnerText value of the element. I.E. TextArea inner text
        /// </summary>
        public TagBuilder InnerText(string value)
        {
            _innerText = value;
            return this;
        }

        /// <summary>
        /// Sets the InnerHtml value of the element
        /// </summary>
        public TagBuilder html(string value)
        {
            return InnerHtml(value);
        }

        /// <summary>
        /// Sets the InnerHtml value of the element
        /// </summary>
        public TagBuilder InnerHtml(string value)
        {
            _innerHtml = value;
            return this;
        }

        /// <summary>
        /// Sets the id attribute of the element
        /// </summary>
        public TagBuilder ID(string value)
        {
            ElementId = value;
            return AddAttribute("id", value);
        }

        /// <summary>
        /// Sets the name attribute of the element
        /// </summary>
        public TagBuilder Name(string value)
        {
            return AddAttribute("name", value);
        }

        /// <summary>
        /// Sets the id and name attribute of the element
        /// </summary>
        public TagBuilder IDandName(string value)
        {
            ID(value);
            Name(value);
            return this;
        }

        /// <summary>
        /// Sets the for attribute of the label element
        /// </summary>
        public TagBuilder @for(string name)
        {
            return AddAttribute("for", name);
        }

        /// <summary>
        /// Sets the href attribute for an anchor tag
        /// </summary>
        public TagBuilder href(string value)
        {
            return AddAttribute("href", value);
        }

        /// <summary>
        /// Sets the type attribute of input element
        /// </summary>
        public TagBuilder type(string inputtype)
        {
            return AddAttribute("type", inputtype);
        }

        /// <summary>
        /// Sets the value attribute of an input element
        /// </summary>
        public TagBuilder value(object val)
        {
            return TagName.ToLower() == "textarea" ? InnerText(GetValue(val)) : AddAttribute("value", GetValue(val));
        }

        /// <summary>
        /// Sets the value attribute of an input element
        /// </summary>
        public TagBuilder valueIf(bool condition, object val)
        {
            if (!condition) return this;
            return TagName.ToLower() == "textarea" ? InnerText(GetValue(val)) : AddAttribute("value", GetValue(val));
        }

        #region Data-Bind

        /// <summary>
        /// Adds a binding attribute to the element
        /// </summary>
        public TagBuilder dataBind(string binding)
        {
            return MergeAttribute("data-bind", binding);
        }

        /// <summary>
        /// Adds a text binding attribute to the element
        /// </summary>
        public TagBuilder bindText(string value)
        {
            return bindProp("text", value);
        }

        /// <summary>
        /// Adds a visible binding attribute to the element
        /// </summary>
        public TagBuilder bindVisible(string value)
        {
            return bindProp("visible", value);
        }

        /// <summary>
        /// Adds a Html binding attribute to the element
        /// </summary>
        public TagBuilder bindHtml(string value)
        {
            return bindProp("html", value);
        }

        /// <summary>
        /// Adds a css binding attribute to the element
        /// </summary>
        public TagBuilder bindCss(string value)
        {
            return bindProp("css", value);
        }

        /// <summary>
        /// Adds a style binding attribute to the element
        /// </summary>
        public TagBuilder bindStyle(string value)
        {
            return bindProp("style", value);
        }

        /// <summary>
        /// Adds a attribute binding attribute to the element
        /// </summary>
        public TagBuilder bindAttr(string value)
        {
            return bindProp("attr", value);
        }

        /// <summary>
        /// Adds a click event binding attribute to the element
        /// </summary>
        public TagBuilder bindClick(string value)
        {
            return bindProp("click", value);
        }

        /// <summary>
        /// Adds a event event binding attribute to the element
        /// </summary>
        public TagBuilder bindEvent(string value)
        {
            return bindProp("event", value);
        }

        /// <summary>
        /// Adds a submit event binding attribute to the element
        /// </summary>
        public TagBuilder bindSubmit(string value)
        {
            return bindProp("submit", value);
        }

        /// <summary>
        /// Adds a enable binding attribute to the element
        /// </summary>
        public TagBuilder bindEnable(string value)
        {
            return bindProp("enable", value);
        }

        /// <summary>
        /// Adds a disable binding attribute to the element
        /// </summary>
        public TagBuilder bindDisable(string value)
        {
            return bindProp("disable", value);
        }

        /// <summary>
        /// Adds a value binding attribute to the element
        /// </summary>
        public TagBuilder bindValue(string value)
        {
            return bindProp("value", value);
        }

        /// <summary>
        /// Adds a checked binding attribute to the element
        /// </summary>
        public TagBuilder bindChecked(string value)
        {
            return bindProp("checked", value);
        }

        /// <summary>
        /// Adds a options  binding attribute to the element
        /// </summary>
        public TagBuilder bindOptions(string list)
        {
            return bindProp("options", list);
        }

        /// <summary>
        /// Adds a options binding attribute to the element
        /// </summary>
        public TagBuilder bindOptions(string list, string optionsText, string optionsValue)
        {
            return bindOptions(list).bindOptionsText(optionsText).bindOptionsValue(optionsValue);
        }

        /// <summary>
        /// Adds a options binding attribute to the element
        /// </summary>
        public TagBuilder bindOptions(string list, string optionsText, string optionsValue, string optionsCaption)
        {
            return bindOptions(list)
                    .bindOptionsText(optionsText)
                    .bindOptionsValue(optionsValue)
                    .bindOptionsCaption(optionsCaption);
        }

        /// <summary>
        /// Adds a options binding attribute to the element
        /// </summary>
        public TagBuilder bindOptions(string list, string optionsText, string optionsValue, string optionsCaption, string value)
        {
            return bindOptions(list)
                    .bindOptionsText(optionsText)
                    .bindOptionsValue(optionsValue)
                    .bindOptionsCaption(optionsCaption)
                    .bindValue(value);
        }

        /// <summary>
        /// Adds a optionsText binding attribute to the element
        /// </summary>
        public TagBuilder bindOptionsText(string value)
        {
            return bindProp("optionsText", JSBuilder.GetString(value));
        }

        /// <summary>
        /// Adds a optionsValue binding attribute to the element
        /// </summary>
        public TagBuilder bindOptionsValue(string value)
        {
            return bindProp("optionsValue", JSBuilder.GetString(value));
        }

        /// <summary>
        /// Adds a optionsCaption binding attribute to the element
        /// </summary>
        public TagBuilder bindOptionsCaption(string value)
        {
            return bindProp("optionsCaption", JSBuilder.GetString(value));
        }

        /// <summary>
        /// Adds a selectedOptions binding attribute to the element. 
        /// The selectedOptions binding controls which elements in a multi-select list are currently selected. This is intended to be used in conjunction with a <select> element and the options binding
        /// </summary>
        public TagBuilder bindSelectedOptions(string value)
        {
            return bindProp("selectedOptions", value);
        }

        /// <summary>
        /// Adds a template binding attribute to the element
        /// </summary>
        public TagBuilder bindTemplate(string templateId)
        {
            return bindTemplate(templateId, null, null, null, null, null);
        }

        /// <summary>
        /// Adds a template binding attribute to the element with a foreach attribute for iteration
        /// </summary>
        public TagBuilder bindTemplate(string templateId, string forEach)
        {
            return bindTemplate(templateId, null, forEach, null, null, null);
            
        }

        /// <summary>
        /// Adds a template binding attribute to the element with a foreach attribute for iteration. pass in an empty value to ignore that setting
        /// </summary>
        public TagBuilder bindTemplate(string templateId, string data, string forEach, string afterAdd, string beforeAdd, string templateOptions)
        {
            var templ = new JSObject();
            templ.Add("name", JSBuilder.GetString(templateId))
                 .AddIfNotEmpty("data", data)
                 .AddIfNotEmpty("foreach", forEach)
                 .AddIfNotEmpty("afterAdd", afterAdd)
                 .AddIfNotEmpty("beforeAdd", beforeAdd)
                 .AddIfNotEmpty("templateOptions", templateOptions);
            return bindTemplate("template", templ.ToString());
        }

        /// <summary>
        /// Binds a certain property (visible, text, etc.) to a certain source value
        /// </summary>
        public TagBuilder bindProp(string propName, string value)
        {
            return MergeAttribute("data-bind", propName + ": " + value);
        }

        #endregion

        /// <summary>
        /// Cleans up the value to add to the html
        /// </summary>
        private string GetValue(object val)
        {
            if (val == null)
            {
                return "";
            }
            if (ReferenceEquals(val.GetType(), typeof(int)) && (int)val == 0)
            {
                return "";
            }
            if (ReferenceEquals(val.GetType(), typeof(decimal)) && (decimal)val == 0)
            {
                return "";
            }
            if (ReferenceEquals(val.GetType(), typeof(double)) && (double)val == 0)
            {
                return "";
            }
            if (ReferenceEquals(val.GetType(), typeof(string)) && string.IsNullOrEmpty(Convert.ToString(val)))
            {
                return "";
            }
            if (ReferenceEquals(val.GetType(), typeof(int?)) && ((int?)val).HasValue == false)
            {
                return "";
            }
            if (ReferenceEquals(val.GetType(), typeof(decimal?)) && ((decimal?)val).HasValue == false)
            {
                return "";
            }
            if (ReferenceEquals(val.GetType(), typeof(double?)) && ((double?)val).HasValue == false)
            {
                return "";
            }

            // if a match was not found then shove the tostring in there
            return val.ToString();
        }

        /// <summary>
        /// Add an onclick event to the tag
        /// </summary>
        /// <param name="value">The javascript script either inline or function call</param>
        public TagBuilder onclick(string value)
        {
            return AddAttribute("onclick", value);
        }

        /// <summary>
        /// Add an onclick event to the tag
        /// </summary>
        /// <param name="value">The javascript script either inline or function call</param>
        public TagBuilder onclick(JSBuilder value)
        {
            return AddAttribute("onclick", value.ToString());
        }

        /// <summary>
        /// Add an onclick event to the tag. Uses string.format
        /// </summary>
        /// <param name="value">The javascript script either inline or function call</param>
        /// <param name="formatValues">Values used in the string.format call</param>
        public TagBuilder onclick(string value, params object[] formatValues)
        {
            return AddAttribute("onclick", string.Format(value, formatValues));
        }

        public TagBuilder onclick(string value, params string[] formatValues)
        {
            return AddAttribute("onclick", string.Format(value, formatValues));
        }

        public TagBuilder onclick(string value, params int[] formatValues)
        {
            return AddAttribute("onclick", string.Format(value, formatValues));
        }

        /// <summary>
        /// Add an onclick event to the tag only if a certain codition is met
        /// </summary>
        /// <param name="condition">only adds the value if the condition is true</param>
        /// <param name="value">The javascript script either inline or function call</param>
        public TagBuilder onclickif(bool condition, string value)
        {
            if (condition) AddAttribute("onclick", value);
            return this;
        }
        
        /// <summary>
        /// Add an onclick event to the tag only if a certain codition is met. Uses string.format
        /// </summary>
        /// <param name="condition">only adds the value if the condition is true</param>
        /// <param name="value">The javascript script either inline or function call</param>
        /// <param name="formatValues">Values used in the string.format call</param>
        public TagBuilder onclickif(bool condition, string value, params string[] formatValues)
        {
            if (condition)
                onclick(value, formatValues);
            return this;
        }

        /// <summary>
        /// Add an onclick event to the tag only if a certain codition is met. Uses string.format
        /// </summary>
        /// <param name="condition">only adds the value if the condition is true</param>
        /// <param name="value">The javascript script either inline or function call</param>
        /// <param name="formatValues">Values used in the string.format call</param>
        public TagBuilder onclickif(bool condition, string value, params int[] formatValues)
        {
            if (condition)
                onclick(value, formatValues);
            return this;
        }

        /// <summary>
        /// Adds javascript to the tag to cancel the enter element
        /// </summary>
        public TagBuilder cancelenter()
        {
            return AddAttribute("onkeypress", "if (event.keyCode == 13) { event.cancelBubble = true; return false; }");
        }

        /// <summary>
        /// Adds javascript to the tag to cancel the enter element
        /// </summary>
        public TagBuilder onEnterKey(string script)
        {
            if (!script.Trim().EndsWith(";")) script += ";";
            return AddAttribute("onkeypress", "if (event.keyCode == 13) { " + script + " return false; }");
        }

        /// <summary>
        /// Add an onchange event to the tag
        /// </summary>
        /// <param name="value">The javascript script either inline or function call</param>
        public TagBuilder onchange(string value)
        {
            return AddAttribute("onchange", value);
        }

        /// <summary>
        /// Add an onmouseover event to the tag
        /// </summary>
        /// <param name="value">The javascript script either inline or function call</param>
        public TagBuilder onmouseover(string value)
        {
            return AddAttribute("onmouseover", value);
        }

        /// <summary>
        /// Add an onmouseover event to the add a style to the tag
        /// </summary>
        public TagBuilder onmouseover_setstyle(string style, string value)
        {
            return onmouseover("jQuery(this).css('" + style + "', '" + value + "');");
        }

        /// <summary>
        /// Add an onmouseout event to add a style to the tag
        /// </summary>
        public TagBuilder onmouseout_setstyle(string style, string value)
        {
            return onmouseover("jQuery(this).css('" + style + "', '" + value + "');");
        }

        /// <summary>
        /// Add an onmouseout event to the tag
        /// </summary>
        /// <param name="value">The javascript script either inline or function call</param>
        public TagBuilder onmouseout(string value)
        {
            return AddAttribute("onmouseout", value);
        }

        /// <summary>
        /// Add the column count to a textarea tag
        /// </summary>
        /// <param name="value">Cols count</param>
        public TagBuilder cols(int value)
        {
            return AddAttribute("cols", value.ToString());
        }

        /// <summary>
        /// Add the rows count to a textarea tag
        /// </summary>
        /// <param name="value">Rows count</param>
        public TagBuilder rows(int value)
        {
            return AddAttribute("rows", value.ToString());
        }

        /// <summary>
        /// Sets the style attribute for the lement. Replaces any other styles previously added. 
        /// To progressively add styles you can use the addStyle method instead
        /// </summary>
        public TagBuilder style(string value)
        {
            return AddAttribute("style", value);
        }

        /// <summary>
        /// Adds the style attribute to the class if not present and appends the current style to it following the correct syntax
        /// </summary>
        /// <param name="value">The style to add i.e. color: white</param>
        public TagBuilder addStyle(string value)
        {
            return MergeAttribute("style", value);
        }
        
        /// <summary>
        /// Adds the title attribute the the class. Used for links
        /// </summary>
        public TagBuilder title(string value)
        {
            return AddAttribute("title", value);
        }

        /// <summary>
        /// Adds a label to the current tag. Useful when adding an input and you want to also add the label for it
        /// </summary>
        /// <param name="value">The text for the label</param>
        public TagBuilder Label(string value)
        {
            _label = value;
            return this;
        }

        /// <summary>
        /// Adds a label to the current tag. Useful when adding an input and you want to also add the label for it
        /// </summary>
        /// <param name="value">The text for the label</param>
        /// <param name="cssclass">Adds this class to the label</param>
        public TagBuilder Label(string value, string cssclass)
        {
            _label = value;
            _labelclass = cssclass;
            return this;
        }

        /// <summary>
        /// When creating a select list or a radio list etc. this adds a collection of ListItems as the InnerHtml of the 
        /// tag as option elements
        /// </summary>
        public TagBuilder options(SelectItem[] items)
        {
            _options.AddRange(items);
            return this;
            //return InnerHtml(GetOptionsString(items, ""));
        }

        /// <summary>
        /// When creating a select list or a radio list etc. this adds a collection of ListItems as the InnerHtml of the 
        /// tag as option elements. You also specify the value of the selected item in the list.
        /// </summary>
        public TagBuilder options(SelectItem[] items, object value)
        {
            this.value(value);
            _options.AddRange(items);
            _selectedOption = (value == null) ? "" : value.ToString();
            return this;
            //this.value(value);
            //var val = (value == null) ? "" : value.ToString();
            //return InnerHtml(GetOptionsString(items, val));
        }

        /// <summary>
        /// When creating a select list or a radio list etc. this adds a collection of ListItems as the InnerHtml of the 
        /// tag as option elements. You also specify the value of the selected item in the list.
        /// </summary>
        public TagBuilder options(SelectItem[] items, int value)
        {
            this.value(value);
            _options.AddRange(items);
            _selectedOption = (value == 0) ? "" : value.ToString();
            return this;
            //this.value(value);
            //var val = (value == 0) ? "" : value.ToString();
            //return InnerHtml(GetOptionsString(items, val));
        }

        /// <summary>
        /// When creating a select list or a radio list etc. This adds a caption item at the top of the list and selects to specify that no item has been selected
        /// </summary>
        public TagBuilder caption(string caption)
        {
            return this.caption(caption, "-1", true);
            
        }

        /// <summary>
        /// When creating a select list or a radio list etc. This adds a caption item at the top of the list and selects to specify that no item has been selected
        /// </summary>
        public TagBuilder caption(string caption, string value, bool selected)
        {
            if (selected) this.value(value);
            _options.Insert(0, new SelectItem(caption, value, selected));
            return this;
        }

        /// <summary>
        /// When creating a select list or a radio list etc. This adds a caption item at the top of the list and selects to specify that no item has been selected
        /// </summary>
        public TagBuilder captionIf(bool condition, string caption)
        {
            if (!condition) return this;
            return this.caption(caption, "-1", true);
        }

        /// <summary>
        /// Adds the checked attribute to the element. Used with checkbox elements.
        /// </summary>
        public TagBuilder @checked(bool value)
        {
            return value ? AddAttribute("checked", "checked") : RemoveAttribute("checked");
        }

        /// <summary>
        /// Adds the checked attribute to the element. Used with input elements.
        /// </summary>
        public TagBuilder @readonly()
        {
            return AddAttribute("ReadOnly", "readonly").AddClass("readonlyField");
        }

        /// <summary>
        /// Adds the src attribute to the element. Used with image elements.
        /// </summary>
        public TagBuilder src(string url)
        {
            return AddAttribute("src", url);
        }

        /// <summary>
        /// Adds the target attribute to the element and sets to blank. Used with anchor elements.
        /// </summary>
        public TagBuilder Target_Blank()
        {
            return AddAttribute("target", "_blank");
        }

        /// <summary>
        /// Adds tabIndex to the tag
        /// </summary>
        public TagBuilder tabIndex(int value)
        {
            return AddAttribute("tabindex", value);
        }

        /// <summary>
        /// Adds tabIndex to the tag
        /// </summary>
        public TagBuilder tabIndex(string value)
        {
            return AddAttribute("tabindex", value);
        }

        public TagBuilder accessKey(int value)
        {
            return AddAttribute("accesskey", value);
        }

        public TagBuilder accessKey(string value)
        {
            return AddAttribute("accesskey", value);
        }

        public TagBuilder accessKey(int value, bool condition)
        {
            if (condition) AddAttribute("accesskey", value);
            return this;
        }

        /// <summary>
        /// This background to the element using a sprite image
        /// </summary>
        /// <param name="img">The sprite image</param>
        /// <param name="bgposition">The x y position of the image to be used in the sprite</param>
        /// <param name="width">The width of the image inside the sprite. sets the width of the element to this</param>
        /// <param name="height">The height of the image inside the sprite. sets the height of the element to this</param>
        public TagBuilder sprite(string img, string bgposition, int width, int height)
        {
            return spriteRollover(img, bgposition, width, height, string.Empty);
        }

        /// <summary>
        /// This adds a rollever effect to the element using a sprite image
        /// </summary>
        /// <param name="img">The sprite image</param>
        /// <param name="bgposition">The x y position of the image to be used in the sprite</param>
        /// <param name="width">The width of the image inside the sprite. sets the width of the element to this</param>
        /// <param name="height">The height of the image inside the sprite. sets the height of the element to this</param>
        /// <param name="rolloverposition">The x y position of the image to be used in the sprite for the rollover. Must be the same width and height as the initial image</param>
        public TagBuilder spriteRollover(string img, string bgposition, int width, int height, string rolloverposition)
        {
            addStyle(string.Format("background-image:url({0}); background-position: {1}; width: {2}px; height: {3}px;", img, bgposition, width, height));
            if (string.IsNullOrEmpty(rolloverposition) == false)
            {
                onmouseover("jQuery(this).css('background-position','" + rolloverposition + "');");
                onmouseout("jQuery(this).css('background-position','" + bgposition + "');");
            }
            return this;
        }

        public TagBuilder spriteRollover(string img, string bgposition, int width, int height, string rolloverposition, string condition)
        {
            addStyle(string.Format("background-image:url({0}); background-position: {1}; width: {2}px; height: {3}px;", img, bgposition, width, height));
            if (string.IsNullOrEmpty(rolloverposition) == false)
            {
                onmouseover("if(" + condition + ") jQuery(this).css('background-position','" + rolloverposition + "');");
                onmouseout("if(" + condition + ") jQuery(this).css('background-position','" + bgposition + "');");
            }
            return this;
        }

        public TagBuilder spriteRollover(string normalClass, string overClass)
        {
            return spriteRollover(normalClass, overClass, false);
        }

        public TagBuilder spriteRollover(string normalClass, string overClass, bool selected)
        {
            AddClass(selected ? overClass : normalClass);
            onmouseover("jQuery(this).removeClass('" + normalClass + "').addClass('" + overClass + "');");
            onmouseout("jQuery(this).removeClass('" + overClass + "').addClass('" + normalClass + "');");
            return this;
        }

        public TagBuilder CollapseFontAndLineHeight()
        {
            return addStyle("font-size:0; line-height:0");
        }

        public TagBuilder showSpriteOnMouseOver(string img, string bgposition)
        {
            return showSpriteOnMouseOver(img, bgposition, false);
        }

        public TagBuilder showSpriteOnMouseOver(string img, string bgposition, bool debug)
        {
            var onscript = string.Format("jQuery(this).css('background-image','url({0})').css('background-position','{1}');", img, bgposition);
            var offscript = "jQuery(this).css('background-image','').css('background-position','');";

            if (debug)
            {
                style("background-position: " + bgposition + "; background-image:url('" + img + "'); ");
                onmouseout(onscript);
                onmouseover(offscript);
            }
            else
            {
                onmouseover(onscript);
                onmouseout(offscript);
            }
            
            return this;
        }

        public TagBuilder addClassOnMouseOver(string className)
        {
            onmouseover("jQuery(this).addClass('" + className + "');");
            onmouseout("jQuery(this).removeClass('" + className + "');");
            return this;
        }

        /// <summary>
        /// Adds a style attribute if not present to the tag and specifies the position as absolute top left using pixels
        /// </summary>
        public TagBuilder absolute(int top, int left)
        {
            return addStyle(string.Format("position: absolute; top: {0}px; left: {1}px", top, left));
        }

        /// <summary>
        /// Adds a style attribute if not present to the tag and specifies the position as absolute top right using pixels
        /// </summary>
        public TagBuilder absoluteTR(int top, int right)
        {
            return addStyle(string.Format("position: absolute; top: {0}px; right: {1}px", top, right));
        }

        /// <summary>
        /// Adds a style attribute if not present to the tag and specifies the position as absolute bottom right using pixels
        /// </summary>
        public TagBuilder absoluteBR(int bottom, int right)
        {
            return addStyle(string.Format("position: absolute; bottom: {0}px; right: {1}px", bottom, right));
        }

        /// <summary>
        /// Adds a style attribute if not present to the tag and specifies the position as absolute bottom left using pixels
        /// </summary>
        public TagBuilder absoluteBL(int bottom, int left)
        {
            return addStyle(string.Format("position: absolute; bottom: {0}px; left: {1}px", bottom, left));
        }

        /// <summary>
        /// Adds a class to the element named causesValidation. This is used by the JSHelper to idenfity when to fire the validation routines.
        /// Usually added to links, buttons and other actionable elements to kick start validation when clicked
        /// </summary>
        public TagBuilder causesValidation()
        {
            return AddClass("causesValidation");
        }

        /// <summary>
        /// Adds a placeholder attribute to the elment. Used by jquery.placeholder for watermarking fields
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public TagBuilder Placeholder(string value)
        {
            return attr("placeholder", value);
        }

        /// <summary>
        /// Adds validation to an input control. Makes the input required
        /// </summary>
        public TagBuilder ValRequired()
        {
            return AddClass("required");
        }

        /// <summary>
        /// Adds validation to an input control. Makes the input required
        /// </summary>
        public TagBuilder ValRequired(string errorMsg)
        {
            return AddClass("required").ValMessage("required", errorMsg);
        }

        /// <summary>
        /// Adds validation to an input control. Makes the input validate agains a remote url. The remore url must return true when valid
        /// </summary>
        public TagBuilder ValRemote(string remoteUrl)
        {
            return AddAttribute("remote", remoteUrl);
        }

        /// <summary>
        /// Adds validation to an input control. Makes the input validate agains a remote url. The remore url must return true when valid
        /// </summary>
        public TagBuilder ValRemote(string remoteUrl, string errorMsg)
        {
            return AddAttribute("remote", remoteUrl).ValMessage("remote", errorMsg);
        }

        /// <summary>
        /// Adds validation to an input control. Makes the input value have a min length
        /// </summary>
        public TagBuilder ValMinLength(int minLength)
        {
            return AddAttribute("minlength", minLength.ToString());
        }

        /// <summary>
        /// Adds validation to an input control. Makes the input value have a min length
        /// </summary>
        public TagBuilder ValMinLength(int minLength, string errorMsg)
        {
            return AddAttribute("minlength", minLength.ToString()).ValMessage("minlength", errorMsg);
        }

        /// <summary>
        /// Adds validation to an input control. Makes the input value have a max length
        /// </summary>
        public TagBuilder ValMaxLength(int maxLength)
        {
            return AddAttribute("maxlength", maxLength.ToString());
        }

        /// <summary>
        /// Adds validation to an input control. Makes the input value have a max length
        /// </summary>
        public TagBuilder ValMaxLength(int maxLength, string errorMsg)
        {
            return AddAttribute("maxlength", maxLength.ToString()).ValMessage("maxlength", errorMsg);
        }

        /// <summary>
        /// Adds validation to an input control. Makes the input value length be between two numbers
        /// </summary>
        public TagBuilder ValRangeLength(int RangeStart, int RangeEnd)
        {
            return AddAttribute("rangelength", string.Format("[{0},{1}]", RangeStart, RangeEnd));
        }

        /// <summary>
        /// Adds validation to an input control. Makes the input value length be between two numbers
        /// </summary>
        public TagBuilder ValRangeLength(int RangeStart, int RangeEnd, string errorMsg)
        {
            return AddAttribute("rangelength", string.Format("[{0},{1}]", RangeStart, RangeEnd)).ValMessage("rangelength", errorMsg);
        }

        /// <summary>
        /// Adds validation to an input control. Makes the input value me no less than than the min specified
        /// </summary>
        public TagBuilder ValMin(int min)
        {
            return AddAttribute("min", min.ToString());
        }

        /// <summary>
        /// Adds validation to an input control. Makes the input value me no less than than the min specified
        /// </summary>
        public TagBuilder ValMin(int min, string errorMsg)
        {
            return AddAttribute("min", min.ToString()).ValMessage("min",errorMsg);
        }

        /// <summary>
        /// Adds validation to an input control. Makes the input value not be greater than the max specified
        /// </summary>
        public TagBuilder ValMax(int max)
        {
            return AddAttribute("max", max.ToString());
        }

        /// <summary>
        /// Adds validation to an input control. Makes the input value not be greater than the max specified
        /// </summary>
        public TagBuilder ValMax(int max, string errorMsg)
        {
            return AddAttribute("max", max.ToString()).ValMessage("max", errorMsg);
        }

        /// <summary>
        /// Adds validation to an input control. Makes the input value be between two numbers
        /// </summary>
        public TagBuilder ValRange(int start, int end)
        {
            return AddAttribute("range", string.Format("[{0},{1}]", start, end));
        }

        /// <summary>
        /// Adds validation to an input control. Makes the input value be between two numbers
        /// </summary>
        public TagBuilder ValRange(int start, int end, string errorMsg)
        {
            return AddAttribute("range", string.Format("[{0},{1}]", start, end)).ValMessage("range", errorMsg);
        }

        /// <summary>
        /// Adds validation to an input control. Makes the input be a valid email address
        /// </summary>
        public TagBuilder ValEmail()
        {
            return AddClass("email");
        }

        /// <summary>
        /// Adds validation to an input control. Makes the input be a valid email address
        /// </summary>
        public TagBuilder ValEmail(string errorMsg)
        {
            return AddClass("email").ValMessage("email", errorMsg);
        }

        /// <summary>
        /// Adds validation to an input control. Makes the input be a valid url address
        /// </summary>
        public TagBuilder ValUrl()
        {
            return AddClass("url");
        }

        /// <summary>
        /// Adds validation to an input control. Makes the input be a valid url address
        /// </summary>
        public TagBuilder ValUrl( string errorMsg)
        {
            return AddClass("url").ValMessage("url", errorMsg);
        }

        /// <summary>
        /// Adds validation to an input control. Makes the input be a valid date
        /// </summary>
        public TagBuilder ValDate()
        {
            return AddClass("date");
        }

        /// <summary>
        /// Adds validation to an input control. Makes the input be a valid date
        /// </summary>
        public TagBuilder ValDate(string errorMsg)
        {
            return AddClass("date").ValMessage("date", errorMsg);
        }

        /// <summary>
        /// Adds date validation to an input control. Makes the input be a valid date based on a certain culture
        /// </summary>
        public TagBuilder ValDate_ForCulture(string culture)
        {
            if (culture.ToLowerInvariant() == "gb")
                return AddClass("dateGB");
            return AddClass("date");
        }

        /// <summary>
        /// Adds validation to an input control. Makes the input be a valid number
        /// </summary>
        public TagBuilder ValNumber()
        {
            return AddClass("number");
        }

        /// <summary>
        /// Adds validation to an input control. Makes the input be a valid number
        /// </summary>
        public TagBuilder ValNumber(string errorMsg)
        {
            return AddClass("number").ValMessage("date", errorMsg);
        }

        public TagBuilder ValInt()
        {
            return ValRegExp("^([0-9]+)$", "Must be a whole number without a decimal point");
        }

        /// <summary>
        /// Adds validation to an input control. Makes the input be a valid credit card
        /// </summary>
        public TagBuilder ValCreditCard()
        {
            return AddClass("creditcard");
        }

        /// <summary>
        /// Adds validation to an input control. Makes the input be a valid credit card
        /// </summary>
        public TagBuilder ValCreditCard(string errorMsg)
        {
            return AddClass("creditcard").ValMessage("creditcard", errorMsg);
        }

        /// <summary>
        /// Adds validation to an input control. Makes the input be a valid digits only
        /// </summary>
        public TagBuilder ValDigits()
        {
            return AddClass("digits");
        }

        /// <summary>
        /// Adds validation to an input control. Makes the input be a valid digits only
        /// </summary>
        public TagBuilder ValDigits(string errorMsg)
        {
            return AddClass("digits").ValMessage("digits", errorMsg);
        }

        /// <summary>
        /// Adds validation to a file input control. Only allows certain extensions for files to upload
        /// </summary>
        public TagBuilder ValAcceptExt(params string[] extensions)
        {
            var extString = "";
            foreach (var e in extensions)
            {
                if (extString.Length > 0)
                    extString += "|";
                extString += e;
            }
            return AddAttribute("accept", extString);
        }

        /// <summary>
        /// Adds validation to a file input control. Only allows certain extensions for files to upload
        /// </summary>
        public TagBuilder ValAcceptExt(string errorMsg, params string[] extensions)
        {
            var extString = "";
            foreach (var e in extensions)
            {
                if (extString.Length > 0)
                    extString += "|";
                extString += e;
            }
            return AddAttribute("accept", extString).ValMessage("accept", errorMsg);
        }

        /// <summary>
        /// Adds validation to an input control. Makes the input value be the same as the value of another input element
        /// </summary>
        public TagBuilder ValEqualTo(string fieldname)
        {
            if (fieldname.StartsWith("#") == false)
            {
                fieldname = "#" + fieldname;
            }
            return AddAttribute("equalTo", fieldname);
        }

        /// <summary>
        /// Adds validation to an input control. Makes the input value be the same as the value of another input element
        /// </summary>
        public TagBuilder ValEqualTo(string fieldname, string errorMsg)
        {
            if (fieldname.StartsWith("#") == false)
            {
                fieldname = "#" + fieldname;
            }
            return AddAttribute("equalTo", fieldname).ValMessage("equalTo", errorMsg);
        }

        /// <summary>
        /// Adds validation to an input control. Validates that the value is not equal to the value provided
        /// </summary>
        public TagBuilder ValNot(string value)
        {
           return AddAttribute("not", value);
        }

        /// <summary>
        /// Adds validation to an input control. Validates that the value is not equal to the value provided
        /// </summary>
        public TagBuilder ValNot(string value, string errorMsg)
        {
            return AddAttribute("not", value).ValMessage("not", errorMsg);
        }

        /// <summary>
        /// Adds validation to an input control. Makes the input be a valid password with a minimum length of 7 and incudes at least 1 non-alpha numeric character
        /// </summary>
        public TagBuilder ValComplexPassword()
        {
            return ValRegExp("^.*(?=.{7,})((?=.*\\d)(?=.*[a-z])|(?=.*\\d)(?=.*[a-z])(?=.*[^a-zA-Z0-9])|(?=.*\\d)(?=.*[^a-zA-Z0-9])|(?=.*[a-z])(?=.*[^a-zA-Z0-9])).*$", "Password must be at least 7 characters, must contain a letter and a number, and must also contain one or more non-alphanumeric character.");
        }
        
        /// <summary>
        /// Adds validation to an input control. Makes the input value match the specified regular expression
        /// </summary>
        public TagBuilder ValRegExp(string expression, string message)
        {
            AddAttribute("regExp", expression);
            return ValMessage("regExp", message);
        }
        
        public TagBuilder ValPassword()
        {
            return ValPassword("Password must be at least 8 characters, must contain a letter and a number, and one or more special characters.");
        }

        public TagBuilder ValPassword(string message)
        {
            return ValRegExp("^.*(?=.{8,})((?=.*\\d)(?=.*[a-z])|(?=.*\\d)(?=.*[a-z])(?=.*[^a-zA-Z0-9])|(?=.*\\d)(?=.*[^a-zA-Z0-9])|(?=.*[a-z])(?=.*[^a-zA-Z0-9])).*$", message);
        }
        
        private readonly Dictionary<string, string> _valMessages = new Dictionary<string, string>();
        /// <summary>
        /// Adds validation messages to be used for validators assiged to this element
        /// </summary>
        /// <param name="validator">This is the validator i.e. required</param>
        /// <param name="message">This is the error message to display</param>
        public TagBuilder ValMessage(string validator, string message)
        {
            _valMessages.Add(validator, message);
            return this;
        }

        /// <summary>
        /// Adds an attribute to the element
        /// </summary>
        /// <param name="key">the name of the attribute i.e. href</param>
        /// <param name="value">the value to assign to the attribute i.e. http://google.com</param>
        public TagBuilder attr(string key, int value)
        {
            return AddAttribute(key, value.ToString());
        }

        /// <summary>
        /// Adds an attribute to the element
        /// </summary>
        /// <param name="key">the name of the attribute i.e. href</param>
        /// <param name="value">the value to assign to the attribute i.e. http://google.com</param>
        public TagBuilder attr(string key, string value)
        {
            return AddAttribute(key, value);
        }

        public TagBuilder attrIf(bool condition, string key, string value)
        {
            return AddAttributeIf(condition, key, value);
        }

       /// <summary>
        /// Adds an attribute to the element
        /// </summary>
        /// <param name="key">the name of the attribute i.e. href</param>
        /// <param name="value">the value to assign to the attribute i.e. http://google.com</param>
        public TagBuilder AddAttribute(string key, int value)
        {
            return AddAttribute(key, value.ToString());
        }

        /// <summary>
        /// Adds an attribute to the element
        /// </summary>
        /// <param name="key">the name of the attribute i.e. href</param>
        /// <param name="value">the value to assign to the attribute i.e. http://google.com</param>
        public TagBuilder AddAttribute(string key, string value)
        {
            if (TagAttributes.ContainsKey(key))
            {
                TagAttributes[key] = value;
            }
            else
            {
                TagAttributes.Add(key, value);
            }
            return this;
        }

        public TagBuilder AddAttributeIf(bool condition, string key, string value)
        {
            if (condition == false)
                return this;
            if (TagAttributes.ContainsKey(key))
            {
                TagAttributes[key] = value;
            }
            else
            {
                TagAttributes.Add(key, value);
            }
            return this;
        }

        /// <summary>
        /// Removes an attribute from the element
        /// </summary>
        /// <param name="key">the name of the attribute to remove i.e. href</param>
        public TagBuilder RemoveAttribute(string key)
        {
            if (TagAttributes.ContainsKey(key))
            {
                TagAttributes.Remove(key);
            }
            return this;
        }

        /// <summary>
        /// Adds an attribute to the element if the attribute has not already been added. If it has then it merges the contents 
        /// of the attribute by appending this value. In the case of the style attribute it knows to to properly merge
        /// styles using the right css syntax
        /// </summary>
        /// <param name="key">the name of the attribute style</param>
        /// <param name="value">the value to assign to the attribute color=white</param>
        public TagBuilder MergeAttribute(string key, string value)
        {

            if (TagAttributes.ContainsKey(key))
            {
                // Merge style attributes with a ;
                if (key == "style")
                {
                    var currentStyle = GetAttribute("style").Trim();

                    if (currentStyle.Length > 0)
                    {
                        if (currentStyle.EndsWith(";") == false)
                            currentStyle += "; ";
                        currentStyle += value;
                    }
                    else
                    {
                        currentStyle = value;
                    }

                    TagAttributes[key] = currentStyle;

                    // add a space for all other merging operations
                }
                else if (key == "data-bind")
                {
                    var currentBinding = GetAttribute("data-bind").Trim();

                    if (currentBinding.Length > 0)
                    {
                        if (currentBinding.EndsWith(",") == false && !value.StartsWith(","))
                            currentBinding += ", ";
                        currentBinding += value;
                    }
                    else
                    {
                        currentBinding = value;
                    }

                    TagAttributes[key] = currentBinding;
                }
                else
                {
                    TagAttributes[key] += " " + value;
                }
            }
            else
            {
                TagAttributes.Add(key, value);
            }
            return this;
        }

        /// <summary>
        /// Returns the value of the specified attribute
        /// </summary>
        /// <param name="attrName">The name of the attribute to retreive</param>
        /// <returns>The value of the attribute</returns>
        public string GetAttribute(string attrName)
        {
            return TagAttributes.ContainsKey(attrName) == false ? string.Empty : TagAttributes[attrName];
        }

        /// <summary>
        /// Format all the added attributes into a properly formatted string
        /// </summary>
        private string GetAttributeString()
        {
            const string attributeFormat = " {0}=\"{1}\"";
            var value = "";
            foreach (var key in TagAttributes.Keys)
            {
                value += string.Format(attributeFormat, key, TagAttributes[key]);
            }
            return value;
        }

        /// <summary>
        /// Generates the label string (only when specified) to use for the element
        /// </summary>
        /// <returns></returns>
        private string GetLabelString()
        {
            if (string.IsNullOrEmpty(_label)) return "";

            var lbl = new TagBuilder("label");
            if (string.IsNullOrEmpty(GetAttribute("id")) == false)
            {
                lbl.@for(GetAttribute("id"));
            }
            lbl.AddClass(string.IsNullOrEmpty(_labelclass) == false ? _labelclass : "fieldlabel");
            if (string.IsNullOrEmpty(_labelWidth) == false)
            {
                lbl.width(_labelWidth);
            }
            lbl.InnerText(_label);
            return "\r\n" + lbl + "\r\n";
        }

        /// <summary>
        /// Generates the html for all the options added to the element to be included as innerHtml
        /// </summary>
        private string GetOptionsString(IEnumerable<SelectItem> items, string val)
        {
            const string optionFormat = " <option value=\"{0}\" {2}>{1}</option>";
            const string selectedString = " selected=\"selected\"";
            var sb = new StringBuilder();
            var valueFound = false;
            foreach (var item in items)
            {
                var selected = false;
                if (item.Selected)
                {
                    selected = true;
                    valueFound = true;
                }
                else if (string.IsNullOrEmpty(val) == false && item.Value.ToUpper() == val.ToUpper())
                {
                    selected = true;
                    valueFound = true;
                }
                sb.AppendLine(string.Format(optionFormat, item.Value, item.Text, (selected ? selectedString : "")));
            }

            // If the value was not found in the options then add it.
            if (string.IsNullOrEmpty(val) == false && valueFound == false)
            {
                sb.AppendLine(string.Format(optionFormat, val, val, selectedString));
            }

            return sb.ToString();
        }

        /// <summary>
        /// Returns the element properly formated as html code. It also recursively renders all child elements
        /// </summary>
        public override string ToString()
        {
            var renderMode = Mode.HasValue ? Mode.Value : RenderMode.Normal;
            return ToString(renderMode);
        }

        /// <summary>
        /// Returns the element properly formated as html code. It also recursively renders all child elements.
        /// Here you spcify in the maner you want the tag to render (Self closing, no closing tag, or normal)
        /// </summary>
        public string ToString(RenderMode mode)
        {
            const string normalTagFormat = "<{0}{1}>{2}</{0}>";
            const string selfCloseTagFormat = "<{0}{1} />";
            const string openTagFormat = "<{0}{1}>{2}";

            // Create the validation messages string
            var valData = "";
            foreach (var m in _valMessages.Keys)
            {
                if (valData.Length > 0)
                    valData += ", ";
                valData += m + ": '" + _valMessages[m] + "'";
            }
            if (valData.Length > 0)
                AddClass("{messages: {" + valData + "}}");

            // Create the options string
            if (_options.Count > 0) InnerHtml(GetOptionsString(_options, _selectedOption));
            
            // Create the inner value
            var innerValue = (string.IsNullOrEmpty(_innerHtml) ? HttpUtility.HtmlEncode(_innerText) : _innerHtml);

            // Render the children into the inner html of the element
            foreach (var c in Children)
            {
                innerValue += c.ToString();
            }

            switch (TagName.ToLower())
            {
                case "input":
                    mode = RenderMode.SelfClose;
                    break;
                case "img":
                    mode = RenderMode.SelfClose;
                    break;
            }

            // Create the tag string
            string tagstring;
            if (mode == RenderMode.SelfClose && string.IsNullOrEmpty(innerValue))
            {
                tagstring = string.Format(selfCloseTagFormat, TagName, GetAttributeString());
            }
            else if (mode == RenderMode.openTagOnly)
            {
                tagstring = string.Format(openTagFormat, TagName, GetAttributeString(), innerValue);
            }
            else
            {
                tagstring = string.Format(normalTagFormat, TagName, GetAttributeString(), innerValue);
            }

            // Return the label (if found) and tag string
            return GetLabelString() + tagstring;
        }

    }
}